package service

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/fs"
	"io/ioutil"
	"os"
	"path"
	"workflow/config"
	"workflow/entity"

	"gopkg.in/yaml.v2"
)

func CreateWorkflow(name string, project_id string, dsl string) (err error) {
	workflow := entity.Workflow{
		Name:      name,
		ProjectID: project_id,
		DSL:       dsl,
		Step:      0,
	}
	err = config.DB.Create(&workflow).Error
	return err
}

func FindWorkflow(id string) (workflow *entity.Workflow, err error) {
	err = config.DB.Where("id = ?", id).First(&workflow).Error
	if err != nil {
		return nil, err
	}
	return workflow, nil
}

// 处理main.yml配置文件
func ParseWorkflow(workflowFile fs.FileInfo) (workflow *entity.Workflow, err error) {
	if !workflowFile.IsDir() {
		return nil, fmt.Errorf("%s 不是一个目录, 跳过", workflowFile.Name())
	}
	content, err := ioutil.ReadFile(path.Join(config.Config.WorkflowDir, workflowFile.Name(), "main.yml"))
	if err != nil {
		return nil, err
	}

	workflow = new(entity.Workflow)

	err = yaml.Unmarshal(content, workflow)
	if err != nil {
		return nil, err
	}
	return workflow, nil
}

func ListWorkflows() (workflows []*entity.Workflow, err error) {
	workflowFiles, err := ioutil.ReadDir(config.Config.WorkflowDir)
	if err != nil {
		return nil, err
	}

	workflows = make([]*entity.Workflow, 0)

	for _, workflowFile := range workflowFiles {
		workflow, err := ParseWorkflow(workflowFile)
		if err == nil {
			workflows = append(workflows, workflow)
		} else {
			fmt.Println(err)
		}
	}
	return workflows, nil
}

func RunWorkflow(id string) {
	var trigger entity.HisotryTrigger
	var workflow entity.Workflow
	var err error

	// 找到trigger
	err = config.DB.Where("id=?", id).First(&trigger).Error
	if err != nil {
		panic(err)
	}

	// 找到对应的Workflow
	err = config.DB.Where("id=?", trigger.WorkflowID).First(&workflow).Error
	if err != nil {
		panic(err)
	}

	workflow.Step = workflow.Step + 1
	err = config.DB.Save(workflow).Error
	if err != nil {
		panic(err)
	}

	// 创建history
	history := entity.History{
		WorkflowID: workflow.ID,
		DSL:        workflow.DSL,
		Num:        workflow.Step,
		TriggerID:  trigger.ID,
	}
	err = config.DB.Create(&history).Error
	if err != nil {
		panic(err)
	}

	dsl, err := ParseDSL(workflow.DSL)
	if err != nil {
		panic(err)
	}

	// 设置history_trigger

	history.Trigger = trigger

	// 创建history_nodes
	history_nodes := make([]entity.HistoryNode, 0)
	for index, node := range dsl.Nodes {
		history_node := entity.HistoryNode{
			HistoryID: history.ID,
			Name:      node.Name,
			Script:    node.Script,
			Step:      index,
		}
		err = config.DB.Create(&history_node).Error
		if err != nil {
			panic(err)
		}
		history_nodes = append(history_nodes, history_node)
	}

	history.Nodes = history_nodes

	// 开始准备工作
	history.Status = "Preparing"
	config.DB.Save(history)

	err = PrepareHistory(trigger, workflow, history)
	if err != nil {
		// 准备失败退出
		history.Status = "ErrorPreparing"
		config.DB.Save(history)
		return
	} else {
		// 已准备
		history.Status = "Prepared"
		config.DB.Save(history)
	}

	// 执行每一个节点
	for index, _ := range history_nodes {
		if index == 0 {
			history.Status = "Running"
			config.DB.Save(history)
		}
		err := RunNode(workflow, history, index)
		if err != nil {
			history.Status = "Error"
			config.DB.Save(history)
			return
		}
	}

	history.Status = "Finished"
	config.DB.Save(history)
}

// 复制所有文件到执行目录
func PrepareHistory(trigger entity.HisotryTrigger, workflow entity.Workflow, history entity.History) (err error) {
	src := path.Join(config.Config.WorkflowDir, workflow.Name)
	dest := path.Join(config.Config.Workdir, workflow.Name, fmt.Sprintf("%d", history.Num))

	err = os.MkdirAll(dest, 0777)
	if err != nil {
		return err
	}

	files, err := ioutil.ReadDir(src)
	if err != nil {
		return nil
	}
	for _, file := range files {
		if !file.IsDir() && file.Name() != "main.yml" {
			source, err := os.Open(path.Join(src, file.Name()))
			if err != nil {
				return err
			}
			defer source.Close()

			destination, err := os.Create(path.Join(dest, file.Name()))
			if err != nil {
				return err
			}
			defer destination.Close()

			io.Copy(destination, source)
			os.Chmod(path.Join(dest, file.Name()), 0777)
		}
	}

	// 写入初始化文件
	dsl, err := ParseDSL(workflow.DSL)
	if err != nil {
		return err
	}

	paramFile, err := os.Create(path.Join(dest, "input_params.txt"))
	if err != nil {
		return err
	}
	defer paramFile.Close()

	objs := make(map[string]interface{})

	switch trigger.Type {
	case "web":
		err = json.Unmarshal([]byte(trigger.Raw), &objs)
		if err != nil {
			return err
		}
		for _, param := range dsl.InputParams {
			if nil == objs[param.Name] {
				objs[param.Name] = param.Value
			}
		}
	case "ticker":
		for _, param := range dsl.InputParams {
			objs[param.Name] = param.Value
		}
	}

	content, err := json.Marshal(objs)
	if err != nil {
		return err
	}
	paramFile.Write(content)

	trigger.InputParams = string(content)
	err = config.DB.Save(trigger).Error
	if err != nil {
		return err
	}

	return nil
}

// 执行 一个节点
func RunNode(workflow entity.Workflow, history entity.History, index int) (err error) {
	var procAttr os.ProcAttr
	node := history.Nodes[index]

	node.Status = "Running"
	args := []string{""}
	err = config.DB.Save(node).Error
	if err != nil {
		return err
	}

	outputPath := path.Join(config.Config.Workdir,
		workflow.Name, fmt.Sprintf("%d", history.Num))

	outputTxtPath := path.Join(outputPath, fmt.Sprintf("%s.output.txt", node.Name))

	var stdoutRead, stdoutWrite *os.File
	var stderrRead, stderrWrite *os.File

	stdoutRead, stdoutWrite, err = os.Pipe()
	if err != nil {
		return err
	}

	stderrRead, stderrWrite, err = os.Pipe()
	if err != nil {
		return err
	}

	procAttr.Env = []string{"LANG=en_US.UTF-8", "PYTHONIOENCODING=utf-8"}
	procAttr.Dir = outputPath
	procAttr.Files = []*os.File{nil, stdoutWrite, stderrWrite}
	proc, err := os.StartProcess(node.Script, args, &procAttr)
	if err != nil {
		return err
	}

	stat, err := proc.Wait()
	if err != nil {
		return err
	}

	stdoutWrite.Close()
	stderrWrite.Close()
	defer stdoutRead.Close()
	defer stderrRead.Close()

	var stdoutBuf bytes.Buffer
	var stderrBuf bytes.Buffer

	io.Copy(&stdoutBuf, stdoutRead)
	node.Stdout = stdoutBuf.String()

	io.Copy(&stderrBuf, stderrRead)
	node.Stderr = stderrBuf.String()

	outputFile, err := os.OpenFile(outputTxtPath, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666)
	if err != nil {
		return err
	}
	defer outputFile.Close()
	_, err = outputFile.WriteString(node.Stdout)
	if err != nil {
		return err
	}

	node.Ret = stat.ExitCode()
	if node.Ret == 0 {
		node.Status = "Finished"
	} else {
		node.Status = "Error"
	}

	err = config.DB.Save(node).Error
	if err != nil {
		return err
	}

	if node.Ret != 0 {
		return fmt.Errorf("Error Ret: %d", node.Ret)
	}

	return nil
}
